import logging
import subprocess
from time import sleep
from timeit import default_timer as timer

from analyzer import Analyzer, TouchAnalyzer
from detector import Detector
from pymemcache import serde
from pymemcache.client.base import Client
from streamer import Streamer
from utils import (check_disk_space, clear_cached_img, get_args,
                   get_detect_info, plot_tracking, save_alert_img, send_alert)


class System:
    def __init__(self, memcache_url, alert_interval):
        logging.info("System init...")
        # used in sleep detect
        self.system_alert_interval = alert_interval
        self.memcache_url = memcache_url
        self.streamer = Streamer()
        self.detector = Detector()
        self.analyzer = None

        # uesd to reduce memcache io, save time
        self.last_check_time = 0
        self.last_alert_time = 0

        # used to record time cost for processing a single frame
        self.cycle_start_time = 0
        self.memcached_pid = 0
        self.detection_status = False
        self.detection_info = {}
        self.sleep_analyzer = Analyzer()
        self.touch_analyzer = TouchAnalyzer()

        self.init_memcached()
        self.memcache_client = Client(
            memcache_url,
            serializer=serde.python_memcache_serializer,
            deserializer=serde.python_memcache_deserializer,
        )
        if not check_disk_space():
            clear_cached_img()
        logging.warning("System init finished.")

    # def __del__(self):
    #     # logging.warning("Memcached closed, trying to stop memcached...")
    #     res = subprocess.run(["kill", self.memcached_pid], capture_output=True)
    #     if res.returncode != 0:
    #         logging.error(
    #             "Failed to stop memcached, please check! System will exit now."
    #         )

    def init_memcached(self):
        memcached_ip, memcached_port = self.memcache_url.split(":")[-2:]
        res = subprocess.run(["lsof", f"-i:{memcached_port}"], capture_output=True)
        res = res.stdout.decode("utf-8").splitlines()
        if len(res) > 1:
            logging.warning("Port is already in use, trying to kill the process...")
            pid = res[1].split()[1]
            res = subprocess.run(["kill", pid])
            if res.returncode != 0:
                logging.error(
                    "Failed to kill the process, please check! System will exit now."
                )
                raise SystemExit
            logging.warning("Process killed, trying to start memcached...")
        res = subprocess.run(
            [
                "memcached",
                "-d",
                "-p",
                memcached_port,
                "-l",
                memcached_ip,
                "-m",
                "16",
                "-u",
                "root",
                "-c",
                "64",
            ],
            capture_output=True,
        )
        if res.returncode != 0:
            logging.error(
                "Failed to start memcached, please check! System will exit now."
            )
            raise SystemExit
        res = subprocess.run(["lsof", f"-i:{memcached_port}"], capture_output=True)
        self.memcached_pid = res.stdout.decode("utf-8").splitlines()[1].split()[1]
        logging.warning("Memcached started, trying to connect...")

    def reset_system(self):
        if not check_disk_space():
            clear_cached_img()
        res, self.detection_info = get_detect_info(self.memcache_client)
        if not res:
            self.detection_status = False
            self.memcache_client.set("DetectionChanged", False)
            return
        logging.warning(f"Detection info: {self.detection_info}")
        self.streamer.set_stream(self.detection_info["url"])
        self.detector.set_detector(self.detection_info["type"])
        if self.detection_info["type"] == "sleep":
            self.analyzer = self.sleep_analyzer
        else:
            self.analyzer = self.touch_analyzer
        self.analyzer.set_infer_args(self.detection_info)
        self.memcache_client.set("DetectionChanged", False)
        self.detection_status = True
        self.last_alert_time = 0
        self.cycle_start_time = timer()
        self.last_check_time = timer()
        if not self.streamer.check_stream():
            logging.error("Stream is not opened, please check the stream path!")
            self.detection_status = False
        return

    def infer(self):
        # TODO this is a very good example to show indent
        ret, frame = self.streamer.get_frame()
        if not ret:
            self.detection_status = False
            return

        objs = self.detector.detect(frame)

        file_frame_interval = self.streamer.get_frame_interval()
        time_cost = (
            file_frame_interval
            if file_frame_interval
            else timer() - self.cycle_start_time
        )
        self.cycle_start_time = timer()
        system_alert, xyxy = self.analyzer.analyze(objs, time_cost, frame)
        if not system_alert:
            return
        if (
            self.detection_info["type"] == "sleep"
            and timer() - self.last_alert_time < self.system_alert_interval
        ):
            return
        if self.detection_info["type"] == "touch":
            frame = self.analyzer.get_error_frame()

        label = "未释放" if self.detection_info["type"] == "touch" else "睡岗"
        procceed_frame = plot_tracking(frame, xyxy, label)
        img_path = save_alert_img(procceed_frame, self.detection_info["type"])
        if get_args("env") != "dev":
            send_alert(img_path, self.detection_info)
        self.last_alert_time = timer()
        if not get_args("save_alert_img"):
            img_path.unlink()

    def run(self):
        while True:
            if timer() - self.last_check_time > 2:
                if self.memcache_client.get("DetectionChanged"):
                    logging.warning(
                        "Config change detected, trying to restart system..."
                    )
                    self.reset_system()
            if not self.detection_status:
                sleep(0.1)
                continue
            self.infer()

    # FIXME update set task function using yaml file

    def set_task(self):
        logging.warning("Setting demo task...")
        detection_info = get_args("test_config")
        self.memcache_client.set("detection_info", detection_info)
        self.memcache_client.set("DetectionChanged", True)
        return
